// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <openssl/asn1.h>
#include <openssl/x509.h>

#include <cstdlib>
#include <iostream>
#include <string>
#include <vector>

#include "base/notreached.h"
#include "base/time/time.h"
#include "components/media_router/common/providers/cast/certificate/cast_cert_reader.h"
#include "components/media_router/common/providers/cast/channel/cast_auth_util_fuzzer_shared.h"
#include "components/media_router/common/providers/cast/channel/fuzz_proto/fuzzer_inputs.pb.h"
// Generated by the "cast_auth_util_fuzzer_certs" data_headers target.
#include "components/test/data/media_router/common/providers/cast/certificate/certificates/chromecast_gen1_data.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
#include "third_party/openscreen/src/cast/common/public/parsed_certificate.h"
#include "third_party/openscreen/src/cast/common/public/trust_store.h"
#include "third_party/openscreen/src/cast/sender/channel/cast_auth_util.h"

namespace cast_channel {
namespace fuzz {
namespace {

using ::cast::channel::CastMessage;
using ::openscreen::cast::TrustStore;

const uint8_t kCertData[] = {
// Generated by //net/data/ssl/certificates:generate_fuzzer_cert_includes
#include "net/data/ssl/certificates/wildcard.inc"
};

static std::vector<std::string>* InitializeCertsOnce() {
  static std::vector<std::string> certs =
      cast_certificate::ReadCertificateChainFromString(
          openscreen::cast::kChromecastGen1);
  CHECK(certs.size() >= 1) << "We should always have at least one certificate.";
  return &certs;
}

static TrustStore* InitializeCastTrustStoreOnce() {
  static TrustStore* cast_trust_store =
      openscreen::cast::CastTrustStore::Create().release();
  return cast_trust_store;
}

static TrustStore* InitializeCastCRLTrustStoreOnce() {
  static TrustStore* crl_trust_store =
      openscreen::cast::CastCRLTrustStore::Create().release();
  return crl_trust_store;
}

time_t GenerateCertTime(TimeBoundCase c, int direction) {
  base::Time t;
  switch (c) {
    case TimeBoundCase::VALID:
      // Create bound that include the current date.
      t = base::Time::Now() + base::Days(direction);
      break;
    case TimeBoundCase::INVALID:
      // Create a bound that excludes the current date.
      t = base::Time::Now() + base::Days(-direction);
      break;
    case TimeBoundCase::OOB:
      // Create a bound so far in the past/future it's not valid.
      t = base::Time::Now() + base::Days(direction * 10000);
      break;
    case TimeBoundCase::MISSING:
      // Remove any existing bound.
      t = base::Time();
      break;
    default:
      NOTREACHED();
  }
  return t.ToTimeT();
}

DEFINE_PROTO_FUZZER(CastAuthUtilInputs& input_union) {
  std::vector<std::string>* certs = InitializeCertsOnce();
  TrustStore* cast_trust_store = InitializeCastTrustStoreOnce();
  TrustStore* crl_trust_store = InitializeCastCRLTrustStoreOnce();

  if (input_union.input_case() !=
      CastAuthUtilInputs::kAuthenticateChallengeReplyInput) {
    return;
  }

  CastAuthUtilInputs::AuthenticateChallengeReplyInput& input =
      *input_union.mutable_authenticate_challenge_reply_input();

  SetupAuthenticateChallengeReplyInput(*certs, &input);

  // Build a well-formed cert with start and expiry times relative to the
  // current time.  The actual cert doesn't matter for testing purposes
  // because validation failures are ignored.
  std::vector<uint8_t> cert_data{std::begin(kCertData), std::end(kCertData)};
  std::unique_ptr<openscreen::cast::ParsedCertificate> peer_cert = std::move(
      openscreen::cast::ParsedCertificate::ParseFromDER(cert_data).value());
  time_t not_before_time = GenerateCertTime(input.start_case(), -1);
  time_t not_after_time = GenerateCertTime(input.expiry_case(), 1);
  peer_cert->SetNotBeforeTimeForTesting(not_before_time);
  peer_cert->SetNotAfterTimeForTesting(not_after_time);

  auto context = openscreen::cast::AuthContext::CreateForTest(input.nonce());

  openscreen::cast::AuthenticateChallengeReply(input.cast_message(), *peer_cert,
                                               context, cast_trust_store,
                                               crl_trust_store);
}

}  // namespace
}  // namespace fuzz
}  // namespace cast_channel
